# -*- coding: utf-8 -*-

import hashlib
import logging
import time
import traceback

from six.moves.urllib.parse import quote

from odoo.http import request

from wechatpy.utils import check_signature, random_string
from wechatpy import WeChatClient
from wechatpy.session.redisstorage import RedisStorage
from wechatpy import WeChatOAuth
from wechatpy import parse_message

from ..utils.common import sentinel
from ..utils.serializer import DefaultSerializer
from ..utils.redis import RedisClient
from ..utils.http import HttpClient
from ..utils.exceptions import WechatMiniAppException
from ..utils.random import Random


logger = logging.getLogger(__name__)


class WechatMPApp(object):
    app_type = 'mpapp'
    site = sentinel
    access_token_url = 'https://api.weixin.qq.com/cgi-bin/token'
    oauth_scope = 'snsapi_userinfo'
    oauth_base_url = 'https://open.weixin.qq.com/connect/'

    def __init__(self, site, app_entry):
        # todo: add error handler to missing of secret_key or session_expiration
        self.secret_key = request.env['ir.config_parameter'].sudo().get_param('wechat.secret_key') or None
        self.session_expiration = int(
            request.env['ir.config_parameter'].sudo().get_param('wechat.session_expiration')) or None
        if not app_entry.settings:
            raise WechatMiniAppException(
                errcode=201002,
                params={
                    'site': site,
                },
            )
        if not app_entry.settings.appid:
            raise WechatMiniAppException(
                errcode=201003,
                params={
                    'site': site,
                },
            )
        if not app_entry.settings.app_secret:
            raise WechatMiniAppException(
                errcode=201004,
                params={
                    'site': site,
                },
            )
        if not app_entry.settings.mp_token:
            raise WechatMiniAppException(
                errcode=201006,
                params={
                    'site': site,
                },
            )
        self._appid = app_entry.settings.appid
        self._app_secret = app_entry.settings.app_secret
        self._signature_token = app_entry.settings.mp_token

        self._serializer = DefaultSerializer(
            key=self.secret_key,
            expiration=self.session_expiration,
        )

        self._redis = RedisClient()
        HttpClient(self)

        session_storage = RedisStorage(self._redis.client, 'wechatmp')
        self.client = WeChatClient(self._appid, self._app_secret, session=session_storage)

        self.oauth_redirect_url = \
            request.env['ir.config_parameter'].sudo().get_param('wechat.oauth_redirect_url')
        self.post_oauth_redirect_url_default = \
            request.env['ir.config_parameter'].sudo().get_param('wechat.post_oauth_redirect_url_default') or '/'

        self.oauth = WeChatOAuth(
            self._appid,
            self._app_secret,
            self.oauth_redirect_url,
            self.oauth_scope,
        )

    def check_signature(self, signature, timestamp, nonce):
        check_signature(self._signature_token, signature, timestamp, nonce)

    def create_menu(self, menu_defs):
        try:
            self.client.menu.create(menu_defs)
        except:
            logger.error('Error while creating menu %s', traceback.format_exc())
            raise WechatMiniAppException(
                errcode=308050,
            )

    def get_js_sdk_config_params(self, url):
        if not url:
            logger.error('EMPTY url for js-sdk config params %s', url)
            raise WechatMiniAppException(
                errcode=309001,
            )

        try:
            jsapi_ticket = self.client.jsapi.get_jsapi_ticket()
            noncestr = random_string(16)
            now_ts = int(time.time())
            signature = self.client.jsapi.get_jsapi_signature(noncestr, jsapi_ticket, now_ts, url)
        except:
            logger.error('Error while retrieving js-sdk config params for url: %s Err: %s', url, traceback.format_exc())
            raise WechatMiniAppException(
                errcode=309050,
            )
        else:
            return {
                'appId': self._appid,
                'timestamp': now_ts,
                'nonceStr': noncestr,
                'signature': signature,
            }

    def oauth_login(self, code):
        try:
            res = self.oauth.fetch_access_token(code)
        except:
            logger.error(
                'Error while fetching access_token for oauth. code: %s Err: %s',
                code,
                traceback.format_exc()
            )
            return None
        logger.info('OAuth returned: %s', res)
        try:
            user_info_raw = self.oauth.get_user_info(res['openid'], res['access_token'])
            user_info = {
                'openid': user_info_raw['openid'],
                'nickname': user_info_raw.get('nickname'),
                'unionid': user_info_raw.get('unionid'),
                'avatar': user_info_raw.get('headimgurl'),
                'gender': user_info_raw.get('sex'),
                'city': user_info_raw.get('city'),
                'province': user_info_raw.get('province'),
                'country': user_info_raw.get('country'),
                'language': user_info_raw.get('language'),
            }
        except:
            logger.error(
                'Error while fetching user info for oauth. access_token: %s Err: %s',
                res,
                traceback.format_exc()
            )
            return {
                'openid': res['openid'],
            }
        logger.info('OAuth user info: %s', user_info)
        return user_info

    def parse_message(self, xml_payload):
        return parse_message(xml_payload)

    def get_oauth_auth_url(self, state=None):
        redirect_uri = quote(self.oauth_redirect_url, safe=b'')
        url_list = [
            self.oauth_base_url,
            'oauth2/authorize?appid=',
            self._appid,
            '&redirect_uri=',
            redirect_uri,
            '&response_type=code&scope=',
            self.oauth_scope
        ]
        if state:
            url_list.extend(['&state=', state])
        url_list.append('#wechat_redirect')
        return ''.join(url_list)

    def generate_oauth_redirect_url(self, referrer):
        if not referrer:
            referrer = self.post_oauth_redirect_url_default
        md5 = hashlib.md5(referrer.encode('utf8')).hexdigest()
        token = Random.generate(8, suffix=md5)
        self._redis.persist_oauth_redirect_token(token, referrer)
        return self.get_oauth_auth_url(token)

    def retrieve_post_oauth_redirect_url(self, token):
        if not token:
            return self.post_oauth_redirect_url_default
        url = self._redis.retrieve_oauth_redirect_url(token)
        if not url:
            url = self.post_oauth_redirect_url_default
        return url
